﻿#include "uart_opt.h"
#include <string>
#include <vector>
#include <windows.h>

struct win_serial_dat {
    HANDLE     serialHandle;
    OVERLAPPED osread;

    uint8_t rd_buff[20 * 1024];
    size_t  rd_offset;
    BOOL    rd_pend;

    OVERLAPPED oswrite;
    BOOL       wr_pend;
};

static uart_hd win_uart_open(const char* devname, uart_par* ipt_par)
{
    auto windat = new win_serial_dat;

    HANDLE& serialHandle = windat->serialHandle;

    serialHandle = CreateFile(devname, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);

    if (INVALID_HANDLE_VALUE == serialHandle) {
        return nullptr;
    }

    uart_par defpar;
    defpar.BaudRate = 115200;
    defpar.ByteSize = 8;
    defpar.Parity   = 0;
    defpar.StopBits = 0;

    uart_par* par = &defpar;
    if (ipt_par) {
        par = ipt_par;
    }

    DCB serialParams       = { 0 };
    serialParams.DCBlength = sizeof(serialParams);

    GetCommState(serialHandle, &serialParams);
    serialParams.BaudRate = par->BaudRate;
    serialParams.ByteSize = par->ByteSize;
    serialParams.StopBits = par->StopBits;
    serialParams.Parity   = par->Parity;
    SetCommState(serialHandle, &serialParams);

    // Set timeouts
    COMMTIMEOUTS timeout                = { 0 };
    timeout.ReadIntervalTimeout         = 50;
    timeout.ReadTotalTimeoutConstant    = 0;
    timeout.ReadTotalTimeoutMultiplier  = 0;
    timeout.WriteTotalTimeoutConstant   = 0;
    timeout.WriteTotalTimeoutMultiplier = 0;

    SetCommTimeouts(serialHandle, &timeout);

    memset(&windat->osread, 0, sizeof(windat->osread));
    memset(&windat->oswrite, 0, sizeof(windat->oswrite));

    windat->osread.hEvent  = CreateEvent(NULL, TRUE, FALSE, NULL);
    windat->oswrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    windat->rd_pend = false;
    windat->wr_pend = false;

    windat->rd_offset = 0;

    return windat;
}

static LPWSTR formated_error(LPWSTR pMessage, DWORD m, ...)
{
    LPWSTR pBuffer = NULL;

    va_list args = NULL;
    va_start(args, pMessage);

    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                  pMessage,
                  m,
                  0,
                  (LPSTR)&pBuffer,
                  0,
                  &args);

    va_end(args);

    return pBuffer;
}

static int _read_buff_dat(win_serial_dat* windat, void* buff, size_t len)
{
    if (windat->rd_offset <= 0) {
        return 0;
    }
    int cplen = windat->rd_offset > len ? len : windat->rd_offset;
    int left  = windat->rd_offset - cplen;
    memcpy(buff, windat->rd_buff, cplen);

    memmove(windat->rd_buff, windat->rd_buff + cplen, left);
    windat->rd_offset = left;
    return cplen;
}

static int win_uart_rd(uart_hd hdl, void* buff, size_t len)
{
    auto  windat = (win_serial_dat*)hdl;
    DWORD rdlen = 0;

    if (windat->rd_offset > 0) {
        return _read_buff_dat(windat, buff, len);
    }

    if (!windat->rd_pend) {
        if (!ReadFile(windat->serialHandle, windat->rd_buff, 10, &rdlen, &windat->osread)) {
            int errcode = GetLastError();
            if (errcode != ERROR_IO_PENDING) {
                printf("read serial err %s\n", (const char*)formated_error(L"%1%0", GetLastError()));
                return -1;
            } else {
                windat->rd_pend = true;
            }
        } else {
            windat->rd_offset = rdlen;
            windat->rd_pend   = false;

            return _read_buff_dat(windat, buff, len);
        }
    }

    if (windat->rd_pend) {
        DWORD dwRes = WaitForSingleObject(windat->osread.hEvent, 1);

        switch (dwRes) {
        case WAIT_OBJECT_0:
            if (!GetOverlappedResult(windat->serialHandle, &windat->osread, &rdlen, FALSE)) {
                // Error in communications; report it.
                return -1;
            } else {
                windat->rd_offset = rdlen;
                windat->rd_pend = false;

                return _read_buff_dat(windat, buff, len);
            }
            break;
        case WAIT_TIMEOUT:
            return -1;
            break;

        default:
            return -1;
            break;
        }
    }
    return -1;
}

static int win_uart_wr(uart_hd hdl, void* buff, size_t len)
{
    auto windat = (win_serial_dat*)hdl;

    DWORD dwWritten;
    BOOL  fRes;
    DWORD dwRes;

    if (!WriteFile(windat->serialHandle, buff, len, &dwWritten, &windat->oswrite)) {
        if (GetLastError() != ERROR_IO_PENDING) {
            // WriteFile failed, but isn't delayed. Report error and abort.
            fRes = FALSE;
        } else {
            dwRes = WaitForSingleObject(windat->oswrite.hEvent, INFINITE);
            // Write is pending.
            switch (dwRes) {
            // OVERLAPPED structure's event has been signaled.
            case WAIT_OBJECT_0:
                if (!GetOverlappedResult(windat->serialHandle, &windat->oswrite, &dwWritten, FALSE)) {
                    fRes = FALSE;
                } else {
                    // Write operation completed successfully.
                    fRes = TRUE;
                }
                break;
            default:
                // An error has occurred in WaitForSingleObject.
                // This usually indicates a problem with the
                // OVERLAPPED structure's event handle.
                fRes = FALSE;
                break;
            }
        }
    } else {
        // WriteFile completed immediately.
        fRes = TRUE;
    }

    return 0;
}

static int win_uart_cl(uart_hd hdl)
{
    auto windat = (win_serial_dat*)hdl;

    CloseHandle(windat->osread.hEvent);
    CloseHandle(windat->oswrite.hEvent);
    CloseHandle(windat->serialHandle);

    delete windat;
    return 0;
}

std::vector<std::string> getComPort();

uart_list_hd* win_uart_list_alloc(void)
{
    auto port_list = getComPort();

    int num = port_list.size();

    if (num > UART_LIST_MAX) {
        num = UART_LIST_MAX;
    }

    auto ret = new uart_list_hd;

    ret->num = num;

    const int maxsz   = sizeof(ret->uart_dev_name);
    const int idx_off = 1;
    const int nul_off = 1;
    int       offset  = 0;

    for (int i = 0; i < num; i++) {
        int len = strlen(port_list[i].data());
        ret->uart_dev_name[offset]                 = len + nul_off;
        ret->uart_dev_name[offset + len + nul_off] = 0;
        memcpy(ret->uart_dev_name + offset + idx_off, port_list[i].data(), len);

        offset += (len + nul_off + idx_off);
    }
    return ret;
}

int win_uart_list_free(uart_list_hd* ipt_list)
{
    delete ipt_list;
    return 0;
}

struct winopt : public uart_opt {
    explicit winopt()
    {
        op = win_uart_open;
        rd = win_uart_rd;
        wr = win_uart_wr;
        cl = win_uart_cl;

        list_alloc = win_uart_list_alloc;
        list_free  = win_uart_list_free;
    }
};

static winopt _winopt;

uart_opt* get_back_end(void)
{
    return &_winopt;
}
